﻿using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using SqlCeEditor.Properties;
using System.Collections.Specialized;
using System.Collections;


namespace SqlCeEditor
{
    public partial class mainForm : Form
    {
        #region Constants
        /// <summary>
        /// Text to display on mainform.
        /// Version number intentionally hardcoded.
        /// </summary>
        const string FORMTEXT = "SQL CE Database Editor";


        #endregion

        StringCollection RecentFiles = new StringCollection();
        ArrayList tsiCollection = new ArrayList();
        SQLParser.SQLParser parser = new SQLParser.SQLParser();

        /// <summary>
        /// Init. If commandline argument is present, open file
        /// </summary>
        /// <param name="cmdLine"></param>
        public mainForm(string cmdLine)
        {
            InitializeComponent();

            this.Text = FORMTEXT;

            //note: namespace mess
            if (SqlCeEditor.Properties.Settings.Default.RecentFiles != null)
                RecentFiles = SqlCeEditor.Properties.Settings.Default.RecentFiles;

            //load recent files
            //threaded, because they may be on net etc and we don't
            //want to stop form to wait for them
            if (RecentFiles.Count > 0)
                backgroundWorker1.RunWorkerAsync();


            treeDb.ExpandAll();

            if (!string.IsNullOrEmpty(cmdLine))
                LoadDatabase(cmdLine);

        }

        /// <summary>
        /// Show query window when clicked on toolbar button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btQuery_Click(object sender, EventArgs e)
        {
            splitterHorizontal.Panel1Collapsed = splitterHorizontal.IsSplitterFixed = !btQuery.Checked;
            edQuery.Select();
        }

        /// <summary>
        /// Show Tools dialog on button click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btTools_Click(object sender, EventArgs e)
        {
            //close query window if it is open
            if (btQuery.Checked)
            {
                btQuery.Checked = false;
            }
            
            string sFilename = dbHandler.dbFilename;

            CleanForClosing();

            (new Tools(sFilename)).ShowDialog();

        }

        /// <summary>
        /// Loading db without the password
        /// </summary>
        /// <param name="fileName"></param>
        private void LoadDatabase(string fileName)
        {
            LoadDatabase(fileName, string.Empty);
        }

        /// <summary>
        /// Invoke database loading procedures
        /// </summary>
        /// <param name="fileName">Database file name</param>
        private void LoadDatabase(string fileName, string password)
        {
            CleanForClosing();

            try
            {
                if (!File.Exists(fileName))
                    throw new Exception(string.Format("File not found:\r\n\r\n{0}", fileName));

                this.Cursor = Cursors.WaitCursor;

                AddToRecentFiles(fileName);


                dbHandler.dbPass = password;

                //Open db here
                if (btQuery.Enabled = dbHandler.OpenConnection(fileName))
                {
                    //update tree to reflect db change
                    treeDb.Nodes[0].Text = Path.GetFileNameWithoutExtension(dbHandler.dbFilename);
                    this.Text = string.Concat(FORMTEXT, " - ", dbHandler.dbFilename);

                    LoadDatabaseToTree();

                }
                else
                {
                    CleanForClosing();
                }

                this.Cursor = Cursors.Default;



            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Unable to open the database");
                btQuery.Enabled = false;
            }

        }


        /// <summary>
        /// Cleans for closing - closes database connection, deletes datasourses and clears nodes, etc
        /// </summary>
        private void CleanForClosing()
        {
            dataGrid.DataSource = null;

            //old code, rewrite at some point?
            treeDb.Nodes[0].Text = "Database";
            treeDb.BeginUpdate();
            treeDb.Nodes[0].Nodes[0].Nodes.Clear();
            treeDb.Nodes[0].Nodes[1].Nodes.Clear();
            treeDb.EndUpdate();

            btQuery.Enabled = false;
            splitterHorizontal.Panel1Collapsed = splitterHorizontal.IsSplitterFixed = !btQuery.Checked;
            dbHandler.CloseConnection();

            this.Text = FORMTEXT;


        }

        /// <summary>
        /// Show dialog, open file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btOpen_Click(object sender, EventArgs e)
        {
            if (!btOpen.DropDown.Visible)

                if (openFileDialog1.ShowDialog() == DialogResult.OK)
                {
                    LoadDatabase(openFileDialog1.FileName);
                }
        }

        /// <summary>
        /// Cleanup on closing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {

                this.Visible = false;
                //save settings
                SqlCeEditor.Properties.Settings.Default.RecentFiles = RecentFiles;
                SqlCeEditor.Properties.Settings.Default.Save();


                CleanForClosing();

            }
            catch { } //there have been issues with closing

        }


        /// <summary>
        /// Load database tables and indexes to the tree
        /// </summary>
        private void LoadDatabaseToTree()
        {
            treeDb.BeginUpdate();
            dbHandler.FillTreeInfo(treeDb.Nodes[0].Nodes[0], treeDb.Nodes[0].Nodes[1]);
            treeDb.EndUpdate();

            treeDb.Nodes[0].Nodes[0].Expand();
            treeDb_AfterSelect(null, new TreeViewEventArgs(treeDb.Nodes[0]));

        }

        /// <summary>
        /// Main treenode selection event.
        /// Rewrite, refactor or just pretend it is good.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeDb_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (!dbHandler.isConnectionOpen)
                return;



            //main node, ie database node
            if (e.Node.Parent == null)
            {
                dataGrid.ReadOnly = true;
                dataGrid.AllowUserToAddRows = dataGrid.AllowUserToDeleteRows = false;
                dataGrid.DataSource = dbHandler.GetDatabaseInfo();
                return;
            }

            //second level nodes. Yes, this is an idiotic way to handle this.
            if (e.Node.Parent.Parent == null)
            {

                dataGrid.ReadOnly = true;
                dataGrid.AllowUserToAddRows = dataGrid.AllowUserToDeleteRows = false;

                dataGrid.SuspendLayout();

                switch (e.Node.Text)
                {
                    case "Tables":
                        dataGrid.DataSource = dbHandler.GetAllTableInfo();
                        break;
                    case "Indexes":
                        dataGrid.DataSource = dbHandler.GetAllIndexesInfo();
                        break;
                    case "Keys":
                        dataGrid.DataSource = dbHandler.GetAllKeysInfo();
                        break;
                    case "Constraints":
                        dataGrid.DataSource = dbHandler.GetAllConstraintsInfo();
                        break;
                }

                dataGrid.ResumeLayout();
                return;
            }
            else
            {
                switch (e.Node.Parent.Text)
                {
                    case "Tables":
                        dataGrid.SuspendLayout();
                        dataGrid.DataSource = dbHandler.LoadTableData(e.Node.Text);
                        dataGrid.ReadOnly = false;
                        dataGrid.AllowUserToAddRows = dataGrid.AllowUserToDeleteRows = true;
                        dataGrid.ResumeLayout();
                        break;

                    case "Indexes":
                        dataGrid.ReadOnly = true;
                        dataGrid.AllowUserToAddRows = dataGrid.AllowUserToDeleteRows = false;
                        dataGrid.DataSource = dbHandler.GetIndexInfo(e.Node.Text);
                        break;

                }


            }
        }

        /// <summary>
        /// Error. Something not right is entered.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dataGrid_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            string s = dbHandler.GetColumnType(treeDb.SelectedNode.Text, dataGrid.Columns[e.ColumnIndex].Name);

            MessageBox.Show("Error changing the data\r\n\r\n" + s +
                Environment.NewLine + Environment.NewLine +
                e.Exception.Message +
                Environment.NewLine + Environment.NewLine +
                e.Context.ToString(),
                "Data error");

            e.Cancel = true; //unneeded?
        }

        /// <summary>
        /// Used when resizing image cells
        /// </summary>
        private struct cellInfo
        {
            public int gHeight;
            public int gWidth;
            public bool resized;
        }


        /// <summary>
        /// Show image bigger or smaller on doubleclick. 
        /// Could be improved
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dataGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (dataGrid.CurrentCell.FormattedValueType.ToString().Contains("Image"))
            {
                DataGridViewImageCell imc = (DataGridViewImageCell)dataGrid.CurrentCell;
                cellInfo ci = new cellInfo();

                if (imc.Tag != null)
                    ci = (cellInfo)imc.Tag;

                if (imc.Tag == null || !ci.resized)
                {
                    //good enough
                    int i = (int)Math.Floor(Math.Sqrt(((byte[])imc.Value).Length));

                    ci.gHeight = dataGrid.CurrentRow.Height;
                    dataGrid.CurrentRow.Height = i;
                    ci.gWidth = dataGrid.Columns[e.ColumnIndex].Width;
                    dataGrid.Columns[e.ColumnIndex].Width = i;
                    imc.ImageLayout = DataGridViewImageCellLayout.Zoom;
                    ci.resized = true;
                }
                else
                {
                    dataGrid.CurrentRow.Height = ci.gHeight;
                    dataGrid.Columns[e.ColumnIndex].Width = ci.gWidth;
                    imc.ImageLayout = DataGridViewImageCellLayout.Normal;
                    ci.resized = false;
                }
                imc.Tag = ci;
            }
        }


        /// <summary>
        /// Clear the query window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btClear_Click(object sender, EventArgs e)
        {
            edQuery.Clear();
        }


        /// <summary>
        /// Execute the query
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btExecute_Click(object sender, EventArgs e)
        {
            if (edQuery.Text.Trim().Length == 0)
                return;

            dataGrid.DataSource = null;

            //start time
            long startTicks = DateTime.Now.Ticks;

            Exception dbEx = null;

            //blech, this should have been done differently,
            //but I kept adding things...
            dataGrid.DataSource = dbHandler.ExecuteManualQuery(edQuery.Text.Trim(), out dbEx, false);

            if (dbEx != null)
            {
                lbResultInfo.ForeColor = Color.Red;
                lbResultInfo.Text = dbEx.Message;

            }
            else
            {
                lbResultInfo.ForeColor = Color.Black;
                TimeSpan ts = new TimeSpan(DateTime.Now.Ticks - startTicks);

                lbResultInfo.Text = string.Format("Query took {0} milliseconds.\r\n\r\n{1} rows", ts.Milliseconds, dataGrid.RowCount);

            }
        }


        /// <summary>
        /// If clicked with ctrl key, don't change focus
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeDb_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
            {
                this.ActiveControl = edQuery;
            }
        }


        /// <summary>
        /// Add text to query
        /// </summary>
        /// <param name="sText"></param>
        private void AddTextToQuery(string sText)
        {
            edQuery.SelectedText = string.Concat("[", sText, "]");
        }

        /// <summary>
        /// Node clicked, used to add node text to the query window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeDb_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
            {
                AddTextToQuery(e.Node.Text);
            }
        }

        /// <summary>
        /// Tables node gets expanded, load column info as child nodes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeDb_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {

            if ((e.Node == null) || (e.Node.Parent == null) || (e.Node.Parent.Text != "Tables"))
                return;
            dataGrid.ReadOnly = true;
            dataGrid.AllowUserToAddRows = dataGrid.AllowUserToDeleteRows = false;
            dataGrid.DataSource = dbHandler.GetTableColumns(e.Node);
        }


        #region Recent files

        /// <summary>
        /// Load recently opened files to btOpen dropdown
        /// using BackgroundWorker, so checking external locations won't
        /// lock the app
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {

            tsiCollection.Clear();
            foreach (string s in RecentFiles)
            {

                if (File.Exists(s))
                {
                    ToolStripMenuItem tsItem = new ToolStripMenuItem(s, iconImages.Images[0]);
                    tsItem.Click += new EventHandler(tsItem_Click);
                    tsiCollection.Add(tsItem);
                }
            }


        }

        /// <summary>
        /// Load file onclick
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tsItem_Click(object sender, EventArgs e)
        {
            LoadDatabase(((ToolStripMenuItem)sender).Text);
        }

        /// <summary>
        /// Add to recentfiles
        /// </summary>
        /// <param name="sFilename"></param>
        private void AddToRecentFiles(string sFilename)
        {
            if (RecentFiles.Contains(sFilename))
                return;

            if (RecentFiles.Count > 10)
                RecentFiles.RemoveAt(RecentFiles.Count);

            RecentFiles.Insert(0, sFilename);
            backgroundWorker1.RunWorkerAsync();

        }

        /// <summary>
        /// BG worker finished, load info to drop-down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btOpen.DropDownItems.Clear();

            btOpen.DropDownItems.AddRange((ToolStripMenuItem[])tsiCollection.ToArray(typeof(ToolStripMenuItem)));
        }

        #endregion


        /// <summary>
        /// On F5, execute the manual query
        /// Needs to be thought a bit, perhaps users will want to refresh something with F5?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mainForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.F5)
            {
                if (!splitterHorizontal.Panel1Collapsed)
                    btExecute_Click(null, null);
            }
        }

        private void btAbout_Click(object sender, EventArgs e)
        {
            (new SqlCeEditor.AboutBox1()).ShowDialog();
        }

        #region Code highlighting



        // 1: Keyword (blue)
        // 3: System Function (green)
        // 5: operator (75,75,75)
        // 6: Text (red)
        // 7: Number (cyan)
        // 8: Comment (magenta)
        // 10: Standard Text (black)

        private static string colorTable = @"{\colortbl;" +
             ConvertColor(System.Drawing.Color.Blue) +
             ConvertColor(System.Drawing.Color.Black) +
             ConvertColor(System.Drawing.Color.Green) +
             ConvertColor(System.Drawing.Color.Black) +
             ConvertColor(System.Drawing.Color.FromArgb(75, 75, 75)) +
             ConvertColor(System.Drawing.Color.Red) +
             ConvertColor(System.Drawing.Color.OrangeRed) +
             ConvertColor(System.Drawing.Color.Gray) +
             ConvertColor(System.Drawing.Color.Black) +
             ConvertColor(System.Drawing.Color.Black) +
             @"}";

        /// <summary>
        /// Code from http://www.istaysharp.net/index.php?title=SQL_Syntax_Highlighting_-_The_Microsoft_Way&printable=yes
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        private static string ConvertColor(System.Drawing.Color color)
        {
            return @"\red" + color.R + @"\green" + color.G + @"\blue" + color.B + ";";
        }




        /// <summary>
        /// Code from http://www.istaysharp.net/index.php?title=SQL_Syntax_Highlighting_-_The_Microsoft_Way&printable=yes
        /// </summary>
        private void ColorSqlSyntax()
        {            
            edQuery.Rtf = @"{\rtf1" + colorTable + @parser.Parse(edQuery.Text, SQLParser.SyntaxConstants.SqlServer) + @"}";

        }
        


        #endregion

        private void edQuery_TextChanged(object sender, EventArgs e)
        {
            edQuery.TextChanged -= edQuery_TextChanged;
            int i = edQuery.SelectionStart;
            ColorSqlSyntax();
            edQuery.SelectionStart = i;
            edQuery.TextChanged += new EventHandler(edQuery_TextChanged);
        }

    }
}
